﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Diagnostics;

namespace EngineOfEvermore.Common
{
    public class PolymorphicSerializer<BASE_CLASS>
    {
        protected readonly Dictionary<XName, Func<BASE_CLASS, XElement>> _serializers =
            new Dictionary<XName, Func<BASE_CLASS, XElement>>();

        protected readonly
            Dictionary<XName, Action<XElement, BASE_CLASS>> _deserializers =
                new Dictionary<XName, Action<XElement, BASE_CLASS>>();

        protected readonly Dictionary<Type, XName> _typeToXmlName =
            new Dictionary<Type, XName>();

        protected readonly Dictionary<XName, Func<BASE_CLASS, bool>> _typeChecker =
            new Dictionary<XName, Func<BASE_CLASS, bool>>();

        protected readonly Dictionary<XName, Func<BASE_CLASS>> _factories =
            new Dictionary<XName, Func<BASE_CLASS>>();

        public void RegisterSerializer<T>( XName elementName,
            Func<BASE_CLASS, XElement> serializer )
            where T : BASE_CLASS, new()
        {
            Debug.Assert( elementName != null );
            Debug.Assert( serializer != null );

            _serializers.Add( elementName, serializer );

            _registerTypeToXmlNameMapping<T>( elementName );
        }

        public void RegisterDeserializer<T>( XName elementName,
            Action<XElement, BASE_CLASS> deserializer )
            where T : BASE_CLASS, new()
        {
            Debug.Assert( elementName != null );
            Debug.Assert( deserializer != null );

            _registerTypeToXmlNameMapping<T>( elementName );

            _deserializers.Add( elementName, deserializer );
        }

        private void _registerTypeToXmlNameMapping<T>( XName elementName )
            where T : BASE_CLASS, new()
        {
            if ( _typeToXmlName.ContainsKey( typeof( T ) ) )
            {
                Debug.Assert( _typeToXmlName[typeof( T )].Equals( elementName ) );
            }
            else
            {
                _typeToXmlName.Add( typeof( T ), elementName );
                _typeChecker.Add( elementName, component => ( component is T ) );
                _factories.Add( elementName, () => new T() );
            }
        }

        public XElement Serialize( BASE_CLASS component )
        {
            XName xmlName;

            if ( !_typeToXmlName.TryGetValue( component.GetType(), out xmlName ) )
            {
                throw new NotSupportedException( "No serializer registered for type: " +
                    component.GetType().FullName );
            }

            Func<BASE_CLASS, XElement> serializer;

            if ( _serializers.TryGetValue( xmlName, out serializer ) )
            {
                return serializer( component );
            }
            else
            {
                throw new NotSupportedException( "No serializer registered for type: " +
                    component.GetType().FullName );
            }
        }

        public BASE_CLASS Deserialize( XElement xmlElement,
            BASE_CLASS component )
        {
            Action<XElement, BASE_CLASS> deserializer;

            if ( _deserializers.TryGetValue( xmlElement.Name, out deserializer ) )
            {
                deserializer( xmlElement, component );

                Debug.Assert( _typeToXmlName[component.GetType()].Equals( xmlElement.Name ) );

                return component;
            }
            else
            {
                throw new NotSupportedException( "No serializer registered for type: " +
                    xmlElement );
            }
        }

        public bool HasSerializerFor<T>( XName elementName )
            where T : BASE_CLASS
        {
            return _serializers.ContainsKey( elementName ) &&
                _typeToXmlName[typeof( T )].Equals( elementName );
        }

        public bool HasDeserializerFor<T>( XName elementName )
        {
            return _deserializers.ContainsKey( elementName ) &&
                 _typeToXmlName[typeof( T )].Equals( elementName );
        }

        public void ClearRegistry()
        {
            _serializers.Clear();
            _deserializers.Clear();
            _typeToXmlName.Clear();
            _typeChecker.Clear();
            _factories.Clear();
        }
    }
}
